Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Peek] Fix memory leak caused by unmanaged bitmaps not being freed #34484

Merged
merged 1 commit into from
Sep 16, 2024

Conversation

daverayment
Copy link
Contributor

@daverayment daverayment commented Aug 29, 2024

Summary of the Pull Request

Fixes the Peek PowerToy leaking unmanaged resources associated with bitmaps when using the ImagePreviewer. Also ensures any Previewer implementing IDisposable will be correctly disposed.

PR Checklist

Detailed Description of the Pull Request / Additional comments

The Peek PowerToy creates a Previewer for each item as the user navigates a folder. For the ImagePreviewer, a Clear method is provided which cleans up unmanaged resources, including bitmaps created for preview thumbnails. Unfortunately, Clear is not called on the old Previewer before a new instance is created, leading to unreachable resources which are never freed. This PR ensures Dispose is called on a Previewer if it implements IDisposable. ImagePreviewer calls Clear within its Dispose, which fixes the leak.

Validation Steps Performed

Manual validation:

  1. Attached to the PowerToys.Peek.UI exe in VS, where the memory leak manifests.
  2. Created a Heap Snapshot.
  3. Manually navigated through a test folder, which contains 90% image files, and also zip files for several minutes.
  4. Created another Heap Snapshot.
  5. Confirmed that the Process Memory used was stable.
  6. Confirmed that only a small number of pinned Byte[] instances were present in the heap snapshot.
  7. Left the program running for more than an hour as I did other work. Confirmed that it still ran without issue and memory issues had not resurfaced.

Before Fix

Despite garbage collection, unmanaged resources are not freed, leading to an ever-increasing amount of memory used - nearly 2GB in this example.

Screenshot 2024-08-28 172800

After Fix

image

@daverayment
Copy link
Contributor Author

@microsoft-github-policy-service agree

@@ -103,12 +103,6 @@
</Page>
</ItemGroup>

<ItemGroup>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like unrelated changes here. Is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was me removing a duplicate entry I found in the project file, and it's not related to the memory leak issue. I can split it out into a separate PR if you'd prefer.

Copy link
Collaborator

@drawbyperpetual drawbyperpetual left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Tested the fix and also performed a sanity check of Peek.

Based on what the fix was, I still don't have a good explanation for what could cause this behaviour we observed earlier, but maybe that's not critical to address now.

It would be good to make the ImagePreviewer::Dispose workflow idempotent, as that is a requirement of IDisposable. Currently, calling Dispose multiple times would result in multiple deletes of native handles. But that does not seem to be happening in practice, so we can defer that for later.

@daverayment
Copy link
Contributor Author

daverayment commented Aug 30, 2024

Thank you for approving the merge, @drawbyperpetual !

I can speak to the strange behaviour you were seeing previously, where the increase in private bytes used was not as prevalent when the same files were previewed again: this could be because of the thumbnail retrieval code in Peek and the relationship with the Windows thumbnail cache. You are 'priming' the cache by previewing the images, as Peek always calls the Shell thumbnail retrieval/creation routine for every image, even when the full size image loads correctly. This was the fundamental 'tell' of the memory leak issue, as the thumbnail bitmaps are unmanaged and never freed. I've recently raised an issue about the unnecessary thumbnail generation as #34490 . In the majority of cases in my testing, the bitmaps are never used, so the #34490 fix actually saves a considerable amount of memory use and subsequent garbage collection (75%+ GC reduction).

I totally agree about the existing Dispose code, too. I'm happy to do that update in a separate PR to match the recommended pattern and ensure idempotency.

@crutkas
Copy link
Member

crutkas commented Sep 7, 2024

@daverayment as FYI, we've been heads down trying to get ship .84 and stabilize based on feedback. We appreciate the PRs here!

@daverayment
Copy link
Contributor Author

@crutkas Thanks! It's a privilege to be able to contribute, and there's certainly no rush. Best of luck with the upcoming release 😀

@jaimecbernardo
Copy link
Collaborator

Thanks a lot for the contribution! Merging this in!

@jaimecbernardo jaimecbernardo merged commit 9bfee34 into microsoft:main Sep 16, 2024
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants